--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
docs/en/plugins.md v4.8.1 (f6cc7b83) Text, 9.42 KB
Tc9d1d9# Plugins
Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time.
Manage them from **Settings → Plugins**. Disable every packaged plugin at startup with Ta5d6ff`--disable-plugins` or Ta5d6ff`MESHCHAT_DISABLE_PLUGINS=true`.
Tc9d1d9## What plugins can do
Tff7b72- Add a row on the **Tools** page
Tff7b72- Add an item in the main **Navigation** sidebar
Tff7b72- React to mesh events (announces, RNS link traffic)
Tff7b72- Call narrowly declared backend managers (path table, debug log, bug reports, RNS links)
Tff7b72- Keep a private key-value store (Ta5d6ff`storage: isolated`)
Tff7b72- Optionally fetch clearnet HTTP (Ta5d6ff`network: fetch`), still subject to **Privacy mode**
Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below).
Tc9d1d9## Runtimes
| Runtime | Where it runs | Trust level |
| --------------- | ------------------------- | ----------------------------------------------- |
| Frontend JS | Browser Web Worker | Medium. Sandboxed worker, capability grants |
| Backend WASM | Ta5d6ff`wasmtime` on the server | Medium. Fuel-metered, capability-gated host |
| Backend Python | In-process with MeshChatX | High. Permission-checked host, still powerful |
| Sideband Ta5d6ff`*.py` | In-process, flat files | Highest. Opt-in danger switch, full host access |
A packaged plugin can ship frontend only, backend only, or both.
Tc9d1d9## Install flow
Ta5d6ff```
Pick ZIP or .wasm file in Settings → Plugins
|
--> Preview (permissions, URLs, signature, findings)
|
--> You grant or deny each capability
|
--> Optional: trust a valid signer
|
--> Install + integrity hash stored
|
--> Enable
|
--> Frontend Worker loads (if present)
--> Backend WASM / Python activates (if present)
```
Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not.
After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered.
Tc9d1d9## Bundled example: Bug Reports
Ta5d6ff`com.meshchatx.mcx-bugs` ships with MeshChatX. It adds a **Bug Reports** tool for sending redacted debug logs to an Ta5d6ff`mcx-bugs-v1` collector, or running a collector yourself.
Layout:
Ta5d6ff```
mcx-bugs/
plugin.json
frontend/main.js
backend/main.py
locales/en.json
```
Use it as the reference package when building your own.
Tc9d1d9## Manifest (`plugin.json`)
Every packaged plugin needs a root Ta5d6ff`plugin.json`.
Ta5d6ff```Ta5d6ffjson
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"com.example.my-plugin"Tb4b4b4,
Tff7b72"version"Tb4b4b4: Ta5d6ff"1.0.0"Tb4b4b4,
Tff7b72"apiVersion"Tb4b4b4: T79c0ff1Tb4b4b4,
Tff7b72"name"Tb4b4b4: Ta5d6ff"My Plugin"Tb4b4b4,
Tff7b72"description"Tb4b4b4: Ta5d6ff"Adds a custom tool."Tb4b4b4,
Tff7b72"frontend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"frontend/main.js"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"js"
Tb4b4b4},
Tff7b72"backend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"backend/main.py"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"python"
Tb4b4b4},
Tff7b72"i18n"Tb4b4b4: Tb4b4b4{
Tff7b72"directory"Tb4b4b4: Ta5d6ff"locales"Tb4b4b4,
Tff7b72"defaultLocale"Tb4b4b4: Ta5d6ff"en"
Tb4b4b4},
Tff7b72"contributes"Tb4b4b4: Tb4b4b4{
Tff7b72"navItems"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"labelKey"Tb4b4b4: Ta5d6ff"nav"
Tb4b4b4}
Tb4b4b4],
Tff7b72"toolsPageEntries"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"name"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"titleKey"Tb4b4b4: Ta5d6ff"title"Tb4b4b4,
Tff7b72"descriptionKey"Tb4b4b4: Ta5d6ff"description"
Tb4b4b4}
Tb4b4b4]
Tb4b4b4},
Tff7b72"permissions"Tb4b4b4: Tb4b4b4{
Tff7b72"hooks"Tb4b4b4: Tb4b4b4[Ta5d6ff"announce.received"Tb4b4b4],
Tff7b72"managers"Tb4b4b4: Tb4b4b4[Ta5d6ff"destinationPath.read"Tb4b4b4],
Tff7b72"storage"Tb4b4b4: Ta5d6ff"isolated"Tb4b4b4,
Tff7b72"network"Tb4b4b4: Ta5d6ff"none"
Tb4b4b4}
Tb4b4b4}
Ta5d6ff```
Notes:
Tff7b72- Ta5d6ff`id` is reverse-DNS style and must stay stable across versions
Tff7b72- Ta5d6ff`apiVersion` is currently Ta5d6ff`1`
Tff7b72- Plugin strings live in the plugin bundle (Ta5d6ff`locales/{locale}.json`), not core Ta5d6ff`en.json`
Tff7b72- Ta5d6ff`contributes` wires UI slots through the frontend registries
Tc9d1d9## Permissions
Nothing is available unless it is declared in the manifest and granted in the install dialog.
Tc9d1d9### Hooks
| Hook | When it fires |
| ------------------- | ----------------------------------------------------------- |
| Ta5d6ff`announce.received` | A Reticulum announce arrives |
| Ta5d6ff`rns.link.event` | Generic RNS Link traffic (Ta5d6ff`packet_received`, Ta5d6ff`link_closed`) |
Hook events reach the UI as WebSocket Ta5d6ff`plugin.event` frames, then into the plugin Worker.
Tc9d1d9### Managers
| Manager | Purpose |
| ---------------------- | ----------------------------- |
| Ta5d6ff`destinationPath.read` | Read the Reticulum path table |
| Ta5d6ff`debugLog.read` | Read redacted debug logs |
| Ta5d6ff`bugReport.*` | Bug report / collector APIs |
| Ta5d6ff`rnsLink.open` | Open or reuse an RNS link |
| Ta5d6ff`rnsLink.identify` | Identify on a cached link |
| Ta5d6ff`rnsLink.request` | Request/response on a link |
| Ta5d6ff`rnsLink.send` | Send a raw link packet |
| Ta5d6ff`rnsLink.close` | Tear down a cached link |
Call managers from a plugin with Ta5d6ff`POST /api/v1/plugins/{id}/invoke` and Ta5d6ff`method: "callManager"`. Details for the link transport are in [Tff7b72RNS Link API](Te6edf3rns-link-api.md).
Tc9d1d9### Storage and network
| Permission | Effect |
| ------------------- | --------------------------------------------------------------------- |
| Ta5d6ff`storage: isolated` | Private key-value store in the MeshChatX database |
| Ta5d6ff`storage: none` | No plugin storage |
| Ta5d6ff`network: fetch` | Outbound HTTP allowed (still blocked by Privacy mode when that is on) |
| Ta5d6ff`network: none` | No clearnet fetch |
Install preview also scans plugin files for external Ta5d6ff`http://` / Ta5d6ff`https://` URLs and shows them before you grant network access.
Tc9d1d9## How a frontend plugin runs
Ta5d6ff```
Settings enable plugin
|
--> PluginHost loads /api/v1/plugins
|
--> Fetch frontend entry as text
|
--> Spawn pluginWorker.js (module Worker)
|
--> Register nav / tools contributions
|
--> Subscribe to plugin.event on /ws (if hooks granted)
|
--> Worker may invoke backend via /api/v1/plugins/{id}/invoke
```
The Worker talks to the host with typed messages (Ta5d6ff`init`, Ta5d6ff`event`, Ta5d6ff`request`). The host never gives the Worker a raw privileged API.
Tc9d1d9## How a backend plugin runs
Ta5d6ff```
Enable plugin
|
+--> type: wasm --> load into wasmtime, fuel + host caps
|
+--> type: python --> import entry, call activate(host)
|
--> Hooks fan out from PluginManager
|
--> invoke(method, args) for RPC from the UI Worker
```
Python host surface (permission-checked):
Tff7b72- Ta5d6ff`host.log(message)`
Tff7b72- Ta5d6ff`host.call_manager(capability, args)`
Tff7b72- Ta5d6ff`host.storage_get(key)` / Ta5d6ff`host.storage_set(key, value)`
Tff7b72- Ta5d6ff`host.network_fetch_allowed()`
Tc9d1d9## Packaging and signing
Distribute as:
Tff7b721. **ZIP** with Ta5d6ff`plugin.json` and assets
Tff7b722. **WASM bundle** (single Ta5d6ff`.wasm` with embedded manifest / files / optional signature)
Signature file for ZIP/dir packages: Ta5d6ff`meshchatx.plugin.rsg`
WASM custom sections:
Ta5d6ff```
meshchatx.plugin --> embedded plugin.json
meshchatx.files --> embedded text assets
meshchatx.signature --> RSG over payload without this section
```
Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload.
Sign and verify with:
Ta5d6ff```Ta5d6ffbash
python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid>
python3 scripts/sign-plugin.py verify-dir ./my-plugin
python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid>
python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid>
python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid>
Ta5d6ff```
Trust status in the UI:
Ta5d6ff```
No .rsg present
--> Unsigned (install allowed)
Valid .rsg, signer unknown
--> Signed (you can add to Trusted Publishers)
Valid .rsg, signer in Trusted Publishers
--> Trusted
Broken / mismatched .rsg
--> Invalid (install blocked)
```
Tc9d1d9## Sideband-compatible plugins
Legacy Sideband-style flat Ta5d6ff`*.py` files are separate from packaged ZIP/WASM plugins.
Ta5d6ff```
Settings → Plugins → Sideband
|
--> Confirm danger prompt
|
--> Set directory of *.py files
|
--> Optional filename.py.rsg next to each script
|
--> Reload
```
These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory.
Tc9d1d9## Operator tips
Tff7b72- Prefer signed packages from publishers you added yourself
Tff7b72- Deny Ta5d6ff`network: fetch` unless the plugin truly needs clearnet
Tff7b72- Prefer WASM backends over Python when you can
Tff7b72- Use Ta5d6ff`--disable-plugins` when diagnosing weird UI or backend behaviour
Tff7b72- Treat Sideband plugins like running arbitrary local scripts
Tc9d1d9## See also
Tff7b72- [Tff7b72Tools and utilities](Te6edf3tools.md) for the Tools page and contribution overview
Tff7b72- [Tff7b72RNS Link API](Te6edf3rns-link-api.md) for Ta5d6ff`rnsLink.*` and Ta5d6ff`rns.link.event`
Tff7b72- [Tff7b72Architecture and design](Te6edf3architecture.md) for the plugin runtime overview
Tff7b72- [Tff7b72Identities, privacy, and security](Te6edf3identity-and-security.md) for signing and Privacy mode
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────